/************************************************************************

								Heroes IV
					  Copyright 2000, The 3DO Company

	------------------------------------------------------------------
  						       skill_properties.h

	$Header: /game/skill_properties.h $

	$NoKeywords: $

 ************************************************************************/

// kill the 255+ character identifier warning.  Templates tend to exceed that easily.
#include "precompiled.h"
#pragma warning( disable: 4786 )

#include "skill_properties.h"

#include <algorithm>
#include <cassert>
#include "bitmap_group_cache.h"
#include "elements_of.h"
#include "enum_map.h"
#include "enum_operations.h"
#include "external_string.h"
#include "skill.h"
#include "string_insensitive_compare.h"
#include "table.h"
#include "table_ptr.h"
#include "town_type.h"

namespace
{
	struct t_skill_properties
	{
		std::string			 keyword;
		std::string			 name;
		std::string			 help_text;
		std::vector<t_skill> requirements;
	};

	struct t_mastery_keyword
	{
		t_skill_mastery mastery;
		char const*     keyword;
	};

	t_char_ptr_pair const k_skill_keywords[] =
	{
		{ k_skill_tactics,		"tactics" },
		{ k_skill_combat_defense,	"toughness" },
		{ k_skill_scouting,		"scouting" },
		{ k_skill_nobility,		"nobility" },
		{ k_skill_life_magic,	"life" },
		{ k_skill_order_magic,	"order" },
		{ k_skill_death_magic,	"death" },
		{ k_skill_chaos_magic,	"chaos" },
		{ k_skill_nature_magic,	"nature" },
		{ k_skill_offense,		"offense" },
		{ k_skill_defense,		"defense" },
		{ k_skill_leadership,	"leadership" },

		{ k_skill_melee,		"combat" },
		{ k_skill_archery,		"archery" },
		{ k_skill_magic_resistance,	"resistance" },

		{ k_skill_pathfinding,		"pathfinding" },
		{ k_skill_seamanship,		"seamanship" },
		{ k_skill_stealth,			"stealth" },

		{ k_skill_estates,			"estates" },
		{ k_skill_mining,			"mining" },
		{ k_skill_diplomacy,		"diplomacy" },

		{ k_skill_healing_magic,	"healing" },
		{ k_skill_spirit_magic,		"spirit" },
		{ k_skill_resurrection,		"resurrection" },

		{ k_skill_enchantment,		"enchantment" },
		{ k_skill_mind_magic,		"mind" },
		{ k_skill_charm,			"charm" },

		{ k_skill_black_magic,		"black" },
		{ k_skill_demonology,		"demonology" },
		{ k_skill_necromancy,		"necromancy" },

		{ k_skill_conjuration,			"conjuration" },
		{ k_skill_destructive_magic,	"pyromancy" },
		{ k_skill_intelligence,			"sorcery" },

		{ k_skill_druid_magic,			"herbalism" },
		{ k_skill_meditation,			"meditation" },
		{ k_skill_recruitment,			"summoning" },
	};

	t_enum_map<t_skill_type> k_skill_type_keywords( k_skill_keywords, 
										ELEMENTS_OF( k_skill_keywords ), 
										k_skill_count );

	t_char_ptr_pair const k_mastery_keywords[] =
	{
		{ k_mastery_basic,		"basic" },
		{ k_mastery_advanced,	"advanced" },
		{ k_mastery_expert,		"expert" },
		{ k_mastery_master,		"master"},
		{ k_mastery_grand_master,	"grandmaster" },
	};
	t_enum_map<t_skill_mastery> k_skill_mastery_keywords( k_mastery_keywords,
									ELEMENTS_OF( k_mastery_keywords ),
									k_mastery_count );


	struct t_sort_properties
	{
		bool operator()( t_skill_properties const& left, t_skill_properties const& right ) const
		{
			return string_insensitive_compare( left.keyword, right.keyword ) < 0;
		}
	};

	struct t_find_properties
	{
		bool operator()( t_skill_properties const& left, std::string const& right ) const
		{
			return string_insensitive_compare( left.keyword, right ) < 0;
		}
	};

	t_skill_properties k_skill_properties[k_skill_count][k_mastery_count];

	t_table_cache g_skill_table( "skills" );

	t_bitmap_group_cache g_skill_82_image_caches[] = 
	{
		"icons.skills.tactics.82",
		"icons.skills.combat.82",
		"icons.skills.scouting.82",
		"icons.skills.nobility.82",
		"icons.skills.life.82",
		"icons.skills.order.82",
		"icons.skills.death.82",
		"icons.skills.chaos.82",
		"icons.skills.nature.82",
	};

	t_bitmap_group_cache g_skill_52_image_caches[] = 
	{
		"icons.skills.tactics.52",
		"icons.skills.combat.52",
		"icons.skills.scouting.52",
		"icons.skills.nobility.52",
		"icons.skills.life.52",
		"icons.skills.order.52",
		"icons.skills.death.52",
		"icons.skills.chaos.52",
		"icons.skills.nature.52",
	};

	t_bitmap_group_cache g_skill_27_image_caches[] = 
	{
		"icons.skills.tactics.27",
		"icons.skills.combat.27",
		"icons.skills.scouting.27",
		"icons.skills.nobility.27",
		"icons.skills.life.27",
		"icons.skills.order.27",
		"icons.skills.death.27",
		"icons.skills.chaos.27",
		"icons.skills.nature.27",
	};

	t_skill_type k_primary_skill[k_skill_count];

	bool         k_is_magic_skill[k_skill_count];

	t_town_type  k_skill_alignments[k_skill_count];
};

// --------------------------------------------------------
// setup dependencies
// --------------------------------------------------------
static void set_requirements( t_skill_type primary, t_skill_type skill,
							  t_skill_mastery const* requirements )
{
	t_skill requirement;
	int     i;

	requirement.skill = skill;
	for (i = 0; i < k_mastery_count; i++)
	{
		if (requirements[i] == k_mastery_none)
			continue;
		requirement.level = requirements[i];
		k_skill_properties[primary][i].requirements.push_back( requirement );
	}
}

static t_skill_mastery const k_simple_requirements[] =
{
	k_mastery_basic, k_mastery_basic, k_mastery_advanced, k_mastery_advanced,
    k_mastery_expert
};

static t_skill_mastery const k_hard_requirements[] =
{
	k_mastery_basic, k_mastery_advanced, k_mastery_expert, k_mastery_master,
	k_mastery_grand_master
};
// --------------------------------------------------------
// setup dependencies
// --------------------------------------------------------
static void set_secular_requirements( t_skill_type primary, t_skill_type const* skills )
{
	k_primary_skill[primary] = primary;
	k_is_magic_skill[primary] = false;
	k_skill_alignments[primary] = k_town_might;
	
	int     i;

	for (i = 0; i < 3; i++)
	{
		k_primary_skill[skills[i]] = primary;
		k_is_magic_skill[skills[i]] = false;
		k_skill_alignments[skills[i]] = k_town_might;
		set_requirements( skills[i], primary, k_simple_requirements );
	}
}

// --------------------------------------------------------
// setup dependencies
// --------------------------------------------------------
static void set_magic_requirements( t_skill_type primary, t_skill_type const* skills )
{
	k_primary_skill[primary] = primary;
	k_is_magic_skill[primary] = true;

	int i;
	for (i = 0; i < 3; i++)
	{
		k_primary_skill[skills[i]] = primary;
		k_is_magic_skill[skills[i]] = true;
		k_skill_alignments[skills[i]] = k_skill_alignments[primary];
	}

	// set up requirements for the primary magic skill
	static t_skill_mastery const k_primary_requirements[2][k_mastery_count] =
	{
		{ k_mastery_none, k_mastery_basic, k_mastery_advanced, k_mastery_expert,
		  k_mastery_grand_master },
		{ k_mastery_none, k_mastery_none,  k_mastery_basic,   k_mastery_expert,
		  k_mastery_grand_master }
	};

	static t_skill_mastery const k_secondary_requirements[2][k_mastery_count] =
	{
		{ k_mastery_basic, k_mastery_advanced, k_mastery_expert, k_mastery_master,
		  k_mastery_master },
		{ k_mastery_advanced, k_mastery_expert, k_mastery_expert, k_mastery_master,
		  k_mastery_master }
	};

	for (i = 0; i < 2; i++)
	{ 
		set_requirements( primary, skills[i], k_primary_requirements[i] );
		set_requirements( skills[i], primary, k_secondary_requirements[i] );
	}
	set_requirements( skills[2], primary, k_simple_requirements );
}

// --------------------------------------------------------
// setup dependencies
// --------------------------------------------------------
static void set_requirements()
{
	t_skill_type const k_secular_skills[][4] =
	{
		{ k_skill_tactics,		k_skill_offense, k_skill_defense, k_skill_leadership },
		{ k_skill_combat_defense,	k_skill_melee, k_skill_archery, k_skill_magic_resistance },
		{ k_skill_scouting,		k_skill_pathfinding, k_skill_seamanship, k_skill_stealth },
		{ k_skill_nobility,		k_skill_estates, k_skill_mining, k_skill_diplomacy },
	};

	t_skill_type const k_magic_skills[][4] = 
	{
		{ k_skill_life_magic,	k_skill_healing_magic, k_skill_spirit_magic, k_skill_resurrection },
		{ k_skill_order_magic,	k_skill_enchantment, k_skill_mind_magic, k_skill_charm },
		{ k_skill_death_magic,	k_skill_black_magic, k_skill_demonology, k_skill_necromancy },
		{ k_skill_chaos_magic,	k_skill_conjuration, k_skill_destructive_magic, k_skill_intelligence },
		{ k_skill_nature_magic,	k_skill_druid_magic, k_skill_meditation, k_skill_recruitment },
	};

	int i;

	for (i = 0; i < ELEMENTS_OF( k_secular_skills ); i++)
		set_secular_requirements( k_secular_skills[i][0], k_secular_skills[i] + 1 );
	for (i = 0; i < ELEMENTS_OF( k_magic_skills ); i++)
		set_magic_requirements( k_magic_skills[i][0], k_magic_skills[i] + 1 );

	// stealth is a special case
	for (i = 0; i <= k_mastery_grand_master; i++)
		k_skill_properties[k_skill_stealth][i].requirements.clear();
	set_requirements( k_skill_stealth, k_skill_scouting, k_hard_requirements );

	t_skill_mastery const tactics_requirements[] =
	{ k_mastery_none, k_mastery_none, k_mastery_basic, k_mastery_advanced, k_mastery_expert };

	t_skill_mastery const combat_requirements[] =
	{ k_mastery_none, k_mastery_none, k_mastery_basic, k_mastery_advanced, k_mastery_master };


	set_requirements( k_skill_tactics, k_skill_offense, tactics_requirements );
	set_requirements( k_skill_tactics, k_skill_defense, tactics_requirements );
	set_requirements( k_skill_scouting, k_skill_pathfinding, combat_requirements );
	set_requirements( k_skill_nobility, k_skill_estates, tactics_requirements );
	set_requirements( k_skill_nobility, k_skill_mining, tactics_requirements );
}

// --------------------------------------------------------
// set properties based on a vector
// --------------------------------------------------------
static void set_properties( std::vector<t_skill_properties>& skill_properties )
{
	// build keyword lists.
	std::string mastery_keywords[k_mastery_count];

	int          i, j;
	std::string  skill_keyword;
	std::string  keyword;
	t_skill_type skill_type;
	std::vector<t_skill_properties>::const_iterator index;

	for (i = 0; i < ELEMENTS_OF( k_mastery_keywords ); i++)
		mastery_keywords[k_mastery_keywords[i].value] = k_mastery_keywords[i].keyword;

	for (skill_type = t_skill_type(0); skill_type < k_skill_count; enum_incr( skill_type ))
	{
		skill_keyword = k_skill_type_keywords[skill_type];
		for (j = 0; j < k_mastery_count; j++)
		{
			keyword = skill_keyword + "_" + mastery_keywords[j];
			k_skill_properties[skill_type][j].keyword = keyword;
			// find skill in skill properties read from table
			index = std::lower_bound( skill_properties.begin(), skill_properties.end(),
				                      keyword, t_find_properties() );
			if (index == skill_properties.end())
				continue;
			if (string_insensitive_compare( keyword, index->keyword ) != 0)
				continue;
			// found it, update it.
			k_skill_properties[skill_type][j] = *index;
		}
	}
}

// --------------------------------------------------------
// read name and help table for skills
// --------------------------------------------------------
enum 
{
	k_column_keyword,
	k_column_name,
	k_column_help_text
};

static void read_table()
{
	// read table
	t_table_ptr						table = g_skill_table.get();
	t_table::iterator				line = table->begin();
	std::vector<t_skill_properties> skill_properties;
	t_skill_properties              properties;

	line++;
	for (line = table->begin(); line != table->end(); line++)
	{
		if ((*line)[0].empty())
			continue;

		properties.keyword =	(*line)[k_column_keyword];
		properties.name =		(*line)[k_column_name];
		properties.help_text =	(*line)[k_column_help_text];
		skill_properties.push_back( properties );
	}
	std::sort( skill_properties.begin(), skill_properties.end(), t_sort_properties() );
	set_properties( skill_properties );
}



// --------------------------------------------------------
// read name and help table for skills
// --------------------------------------------------------
static void read_data()
{
	static bool initialized = false;

	if (initialized)
		return;

	initialized = true;

	// read table
	read_table();

	// Set up primary magic alignments
	k_skill_alignments[k_skill_life_magic] = k_town_life;
	k_skill_alignments[k_skill_order_magic] = k_town_order;
	k_skill_alignments[k_skill_death_magic] = k_town_death;
	k_skill_alignments[k_skill_chaos_magic] = k_town_chaos;
	k_skill_alignments[k_skill_nature_magic] = k_town_nature;

	set_requirements();
}

// --------------------------------------------------------
// Return the alignment corresponding to a skill
// --------------------------------------------------------
t_town_type get_skill_alignment( t_skill_type skill )
{
	read_data();

	return k_skill_alignments[skill];
}

// --------------------------------------------------------
// skill properties
// --------------------------------------------------------
t_skill_type get_primary_skill( t_skill_type skill_type )
{
	read_data();

	return k_primary_skill[skill_type];
}

// --------------------------------------------------------
// skill properties
// --------------------------------------------------------
std::string get_keyword( t_skill const& skill )
{
	read_data();

	return k_skill_properties[skill.skill][skill.level].keyword;
}

// --------------------------------------------------------
// skill properties
// --------------------------------------------------------
t_skill_type get_skill_type( std::string const& keyword )
{
	return k_skill_type_keywords[keyword];
}

// --------------------------------------------------------
// skill properties
// --------------------------------------------------------
std::string get_keyword( t_skill_type skill )
{
	return k_skill_type_keywords[skill];
}

std::string get_mastery_keyword( t_skill_mastery level )
{
	return k_skill_mastery_keywords[level];
}

// --------------------------------------------------------
// skill properties
// --------------------------------------------------------
std::string        get_name( t_skill const& skill )
{
	read_data();

	return k_skill_properties[skill.skill][skill.level].name;
}

// --------------------------------------------------------
// skill properties
// --------------------------------------------------------
std::string get_name( t_skill_type type )
{
	assert( type >= 0 && type < k_skill_type_count );

	static t_external_string const k_type_names[ k_skill_type_count ] =
	{
		t_external_string( "Tactics.skill" ),
		t_external_string( "Toughness.skill" ),
		t_external_string( "Scouting.skill" ),
		t_external_string( "Nobility.skill"),
		t_external_string( "Life_Magic.skill"),
		t_external_string( "Order_Magic.skill"),
		t_external_string( "Death_Magic.skill"),
		t_external_string( "Chaos_Magic.skill"),
		t_external_string( "Nature_Magic.skill"),
		t_external_string( "Offense.skill"),
		t_external_string( "Defense.skill"),
		t_external_string( "Leadership.skill"),
		t_external_string( "Combat.skill"),
		t_external_string( "Archery.skill"),
		t_external_string( "Magic_Resistance.skill"),
		t_external_string( "Pathfinding.skill"),
		t_external_string( "Seamanship.skill"),
		t_external_string( "Stealth.skill"),
		t_external_string( "Estates.skill"),
		t_external_string( "Mining.skill"),
		t_external_string( "Diplomacy.skill"),
		t_external_string( "Healing.skill"),
		t_external_string( "Spirituality.skill"),
		t_external_string( "Resurrection.skill"),
		t_external_string( "Enchantment.skill"),
		t_external_string( "Wizardry.skill"),
		t_external_string( "Charm.skill"),
		t_external_string( "Occultism.skill"),
		t_external_string( "Demonology.skill"),
		t_external_string( "Necromancy.skill"),
		t_external_string( "Conjuration.skill"),
		t_external_string( "Pyromancy.skill"),
		t_external_string( "Sorcery.skill"),
		t_external_string( "Herbalism.skill"),
		t_external_string( "Meditation.skill"),
		t_external_string( "Summoning.skill"),
	};

	return k_type_names[ type ];
}

// --------------------------------------------------------
// skill properties
// --------------------------------------------------------
std::string        get_help_text( t_skill const& skill )
{
	read_data();
	return k_skill_properties[skill.skill][skill.level].help_text;
}

// --------------------------------------------------------
// skill properties
// --------------------------------------------------------
t_bitmap_group_ptr get_skill_icons( t_skill_type skill, int height )
{
	read_data();

	skill = get_primary_skill( skill );
	if (height < 52)
		return g_skill_27_image_caches[skill].get();
	if (height < 82)
		return g_skill_52_image_caches[skill].get();
	return g_skill_82_image_caches[skill].get();
}

// --------------------------------------------------------
// skill properties
// --------------------------------------------------------
std::vector<t_skill> const& get_requirements( t_skill const& skill )
{
	read_data();

	return k_skill_properties[skill.skill][skill.level].requirements;
}

// --------------------------------------------------------
// Is this a magic skill?
// --------------------------------------------------------
bool is_magic_skill(t_skill_type type)
{
	read_data();

	return k_is_magic_skill[type];
}

// --------------------------------------------------------
// Is this a primary skill?
// --------------------------------------------------------
bool is_primary_skill(t_skill_type type)
{
	return get_primary_skill(type) == type;
}

// --------------------------------------------------------
// charm surrender percentage
// --------------------------------------------------------
int	get_charm_surrender_percentage( t_skill_mastery type )
{
	static int const k_surrender_percentage[k_mastery_count+1] = { 100, 90, 80, 75, 70, 65 };

	return k_surrender_percentage[type+1];
}

// --------------------------------------------------------
// diplomacy surrender percentage
// --------------------------------------------------------
int	get_diplomacy_surrender_percentage( t_skill_mastery type )
{
	static int const k_diplomacy_percentage[k_mastery_count+1] = { 100, 80, 70, 60, 55, 50 }; 

	return k_diplomacy_percentage[type+1];
}

